package com.nulldev.util.web.YouTubeExtractor.extractors.util;

import java.util.List;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;

import com.nulldev.util.VariableAPI.ArrayUtils;
import com.nulldev.util.VariableAPI.MathUtil;
import com.nulldev.util.VariableAPI.StringsUtil;
import com.nulldev.util.concurrency.threadIt.v4.asyncTasks.AsyncTaskCallback;
import com.nulldev.util.concurrency.threadIt.v4.emapi.ExecutorManager;
import com.nulldev.util.concurrency.threadIt.v4.emapi.IExecutorManager;
import com.nulldev.util.concurrency.threadIt.v4.emapi.uft.Executable;
import com.nulldev.util.data.variable;
import com.nulldev.util.languages.json.uniapi.UniJsonValue;
import com.nulldev.util.logging.Logger;
import com.nulldev.util.logging.LoggerFactory;
import com.nulldev.util.web.URLUtils.URLQueryParser;
import com.nulldev.util.web.URLUtils.URLQueryParser.URLQueryParserOptions;
import com.nulldev.util.web.YouTubeExtractor.YouTubeExtractor;
import com.nulldev.util.web.YouTubeExtractor.formats.Formats;
import com.nulldev.util.web.YouTubeExtractor.signatures.extractor.Extractor;
import com.nulldev.util.web.YouTubeExtractor.video.YouTubeVideo;
import com.nulldev.util.web.YouTubeExtractor.video.YouTubeVideo.FormatType;

public class SharedExtractionFunctions {

	private static final Logger logger = LoggerFactory.getLogger();
	private static final IExecutorManager td = ExecutorManager.get();
	private static final String[] DISALLOWED_TYPES = new String[0];

	public static void generate_legacy_video(final BlockingQueue<Executable<YouTubeVideo>> queue, final List<YouTubeVideo> results, final UniJsonValue v,
			final Executable<Extractor> extractorTask, final variable<Boolean> isVR180, final boolean use_v9_deciphering, final boolean ECD_ENABLED) {
		if (v == null || v.isNull()) {
			logger.warn("Dropped legacy format: " + v + " [reason=null]");
			return;
		}
		final Executable<YouTubeVideo> o = td.asyncSubmit(() -> {
			try {
				String vurl = v.getString("url", null);
				final boolean ciphered = v.contains("cipher") || v.contains("signatureCipher");
				if (ciphered) {
					final Map<String, String> y = URLQueryParser.extract(StringsUtil.replace(v.getStrings("signatureCipher", "cipher"), "\\u0026", "&"),
							URLQueryParserOptions.PREFER_LEGACY_IF_ACCEL_V1, URLQueryParserOptions.TREAT_URL_AS_QUERY);
					logger.trace("cipher: " + y.get("s"));
					vurl = y.get("url");
					if (vurl == null) {
						logger.warn("Decipher URL was null, skipped.");
						return null;
					}
					if (vurl.contains("&dur=0.000")) {
						logger.warn("A source had zero duration, skipped.");
						return null;
					}
					final Extractor f = extractorTask.waitTillCompletion();
					if (f == null) {
						logger.warn("Extractor was null! Avoiding decipher.");
						return null;
					}
					final String x = f.decrypt(y.get("s"));
					logger.trace("deciphered: " + x);
					vurl += "&" + y.get("sp") + "=" + x;
				}
				if (vurl == null) {
					logger.error("A source was missing it's URL and/or cipher, skipped.");
					logger.error(v.toString());
					return null;
				}
				if (vurl.contains("&dur=0.000")) {
					logger.warn("A source had zero duration, skipped.");
					return null;
				}
				final boolean has_v9 = vurl.contains("&n=") && use_v9_deciphering;
				if (has_v9) {
					final String n = StringsUtil.substringBetweenO(vurl, "&n=", "&");
					final String processed = YouTubeExtractor.dfs_handler.getAuto().decrypt(n);
					vurl = vurl.replace("&n=" + n, "&n=" + processed);
				}
				final int itag = v.getInt("itag");
				return new YouTubeVideo(vurl, false, itag, Formats.auto(itag, new UniJsonValue(v), vurl))
						.setCodecMime(StringsUtil.replace(v.getString("mimeType", ""), "%3B", ";")).setFileSize(__get_video_size(v, vurl));
			} catch (Exception e) {
				logger.error("Failed to process legacy format!", e);
				return null;
			}
		});
		try {
			if (!queue.offer(o.addCallback(new AsyncTaskCallback<YouTubeVideo>() {

				@Override
				public void onCompletion(final YouTubeVideo result) {
					if (result == null) {
						logger.warn("[legacy-formats] Got a null pointer as a video result, skipping.");
						return;
					}
					if (!results.add(result))
						throw new RuntimeException("Failed to add queued decipher task!");
				}

				@Override
				public void onError(final Throwable e) {
					logger.error(e);
				}
			}), 500, TimeUnit.MILLISECONDS)) {
				logger.debug("Rejected: " + o.waitFor(500, TimeUnit.MILLISECONDS));
				throw new RuntimeException("Failed to add queued decipher task!");
			}
		} catch (Exception e) {
			throw new RuntimeException("Failed to add queued decipher task! [e=f]", e);
		}
	}

	public static void generate_adaptive_video(final BlockingQueue<Executable<YouTubeVideo>> queue, final List<YouTubeVideo> results, final UniJsonValue v,
			final Executable<Extractor> extractorTask, final variable<Boolean> isVR180, final boolean use_v9_deciphering, final boolean ECD_ENABLED) {
		if (v == null || v.isNull()) {
			logger.warn("Dropped adaptive format: " + v + " [reason=null]");
			return;
		}
		try {
			final Executable<YouTubeVideo> o = td.asyncSubmit(() -> {
				try {
					String vurl = v.getString("url", null);
					final boolean ciphered = v.contains("cipher", "signatureCipher");
					if (ciphered) {
						final Map<String, String> y = URLQueryParser.extract(StringsUtil.replace(v.getStrings("signatureCipher", "cipher"), "\\u0026", "&"),
								URLQueryParserOptions.PREFER_LEGACY_IF_ACCEL_V1, URLQueryParserOptions.TREAT_URL_AS_QUERY);
						logger.trace("cipher: " + y.get("s"));
						vurl = y.get("url");
						if (vurl == null) {
							logger.warn("Decipher URL was null, skipped.");
							return null;
						}
						final Extractor f = extractorTask.waitTillCompletion();
						if (f == null) {
							logger.warn("Extractor was null! Avoiding decipher.");
							return null;
						}
						final String x = f.decrypt(y.get("s"));
						logger.trace("deciphered: " + x);
						vurl += "&" + y.get("sp") + "=" + x;
					}
					if (vurl == null) {
						logger.error("A source was missing it's URL and/or cipher, skipped.");
						logger.error(v.toString());
						return null;
					}
					if (vurl.contains("&dur=0.000")) {
						logger.warn("A source had zero duration, skipped.");
						return null;
					}
					final boolean has_v9 = vurl.contains("&n=") && use_v9_deciphering;
					if (has_v9) {
						final String n = StringsUtil.substringBetweenO(vurl, "&n=", "&");
						final String processed = YouTubeExtractor.dfs_handler.getAuto().decrypt(n);
						vurl = vurl.replace("&n=" + n, "&n=" + processed);
					}
					final int itag = v.getInt("itag");
					if (v.contains("projectionType") && (itag != 251 && itag != 250 && itag != 249 && itag != 140)) {
						isVR180.set(!v.getString("projectionType", "").equalsIgnoreCase("RECTANGULAR"));
					}
					final String type = v.getString("type", "");
					final String mime = StringsUtil.replace(v.getString("mimeType", ""), "%3B", ";");
					if (ArrayUtils.contains(DISALLOWED_TYPES, type)) {
						logger.error("Cannot handle type: " + type);
						logger.warn("Object: " + v);
						return null;
					} else if (type.equalsIgnoreCase("FORMAT_STREAM_TYPE_OTF")) {
						return new YouTubeVideo(vurl, true, itag, Formats.auto(itag, new UniJsonValue(v), vurl)).setCodecMime(mime)
								.setFileSize(v.getLong("contentLength", -1)).setSize(v.getInt("width", 0) + "x" + v.getInt("height", 0))
								.setFPS(v.getInt("fps", -1)).setBitrate(v.getInt("bitrate", 80000)).setFormatType(FormatType.OTF_STREAM)
								.setECDCodecsFromMime(ECD_ENABLED ? mime : null);
					} else {
						return new YouTubeVideo(vurl, true, itag, Formats.auto(itag, new UniJsonValue(v), vurl)).setCodecMime(mime)
								.setFileSize(v.getLong("contentLength", -1)).setSize(v.getInt("width", 0) + "x" + v.getInt("height", 0))
								.setInitPayload(v.getSafe("initRange").getString("start", null) + "-" + v.getSafe("initRange").getString("end", null))
								.setIndexPayload(v.getSafe("indexRange").getString("start", null) + "-" + v.getSafe("indexRange").getString("end", null))
								.setFPS(v.getInt("fps", -1)).setBitrate(v.getInt("bitrate", 80000)).setECDCodecsFromMime(ECD_ENABLED ? mime : null);
					}
				} catch (Exception e) {
					logger.error("Failed to process adaptive format!", e);
					return null;
				}
			});
			if (!queue.offer(o.addCallback(new AsyncTaskCallback<YouTubeVideo>() {

				@Override
				public void onCompletion(final YouTubeVideo result) {
					if (result == null) {
						logger.warn("[adaptive-formats] Got a null pointer as a video result, skipping.");
						return;
					}
					if (!results.add(result))
						throw new RuntimeException("Failed to add queued decipher task!");
				}

				@Override
				public void onError(final Throwable e) {
					logger.error(e);
				}
			}), 200, TimeUnit.MILLISECONDS)) {
				logger.debug("Rejected: " + o.waitFor(200, TimeUnit.MILLISECONDS));
				throw new RuntimeException("Failed to add queued decipher task!");
			}
		} catch (Exception ec) {
			logger.error("Failed to process adaptive format!", ec);
		}
	}

	private static long __get_video_size(final UniJsonValue v, final String url) {
		if (v.contains("contentLength")) {
			return v.getLong("contentLength", -1);
		} else if (url.contains("&clen=")) {
			final String l = StringsUtil.substringBetweenO(url, "&clen=", "&");
			return MathUtil.safeParseL(l, -1);
		} else {
			/* How... */
			return -1;
		}
	}
}
